package com.kcht.np.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.kcht.np.common.exception.JwtAuthenticationException;
import com.kcht.np.common.exception.ParameterException;
import com.kcht.np.common.exception.UserExistsException;
import com.kcht.np.common.jwt.JwtTokenConverter;
import com.kcht.np.common.jwt.JwtUser;
import com.kcht.np.constant.GenderConstant;
import com.kcht.np.constant.InitData;
import com.kcht.np.constant.UserStatus;
import com.kcht.np.core.domain.entity.SysMenu;
import com.kcht.np.entity.Relation;
import com.kcht.np.entity.SysDepartment;
import com.kcht.np.entity.SysRole;
import com.kcht.np.entity.SysUser;
import com.kcht.np.mapper.SysDepartmentMapper;
import com.kcht.np.mapper.SysRoleMapper;
import com.kcht.np.mapper.SysUserMapper;
import com.kcht.np.service.*;
import com.kcht.np.util.JsonResult;
import com.kcht.np.vo.ButtonVo;
import com.kcht.np.vo.MenuVo;
import com.kcht.np.vo.menu.RouterVo;
import com.kcht.np.vo.page.QueryParameter;
import com.kcht.np.vo.user.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
  * <p>
  * 用户 Service 实现类
  * </p>
  *
  * @Package:        com.kcht.np.mp.service.impl
  * @Description:    用户 Service 实现类
  * @Author:         liuqi
  * @CreateDate:     2020/3/31 21:25
 */

@Slf4j
@Service
@DS("kchtnp")
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenConverter jwtTokenConverter;

    @Autowired
    @Qualifier("JwtUserDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    @Autowired
    private SysPermissionService sysPermissionService;

    @Autowired
    private SysRoleService sysRoleService;

    @Autowired
    private SysRoleMapper roleMapper;

    @Autowired
    private SysDepartmentService departmentService;

    @Autowired
    private SysDepartmentMapper departmentMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private ISysMenuService menuService;

    @Autowired
    @Lazy
    private RedisTemplate<String, Object> redisTemplate;

    private BoundHashOperations<String, String, JwtUser> tokenStorage() {
        return redisTemplate.boundHashOps(jwtTokenConverter.getTokenHeader());
    }

    /**
     * @Description: 登录表单获取 Token
     * @param:       [username, password]
     * @return:      java.lang.String
     * @Author:      liuqi
     * @CreateDate:  2020/4/1 0:15
     * @throws:      AuthenticationException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String login(String username, String password) throws AuthenticationException {
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = authenticationManager.authenticate(upToken);
        JwtUser userDetails = (JwtUser) authenticate.getPrincipal();
        /*生成token*/
        String token = jwtTokenConverter.generateToken(userDetails);
        log.debug("userDetails: {}", userDetails);
        tokenStorage().put(userDetails.getUsername(), userDetails);
        return token;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void logout(){
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(principal=="anonymousUser"){
             throw new JwtAuthenticationException(JsonResult.HttpStatus.UNAUTHORIZED.getValue());
        }else{
            JwtUser jwtUser=(JwtUser) principal;
            tokenStorage().delete(jwtUser.getUsername());
        }
    }

    @Override
    public JwtUser getJwtUser(String username) throws AuthenticationException {
        JwtUser jwtUser = tokenStorage().get(username);
        if (jwtUser == null) {
            jwtUser=(JwtUser) userDetailsService.loadUserByUsername(username);
            tokenStorage().put(jwtUser.getUsername(), jwtUser);
        }
        return jwtUser;
    }

    @Override
    public UserVo userQueryPersonalInfo() {
        // 从SecurityContextHolder中获取到，当前登录的用户信息。
        JwtUser userDetails= (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        // 根据用户Id，获取用户详细信息。
        SysUser sysUser = getById(userDetails.getUid());
        UserVo result = new UserVo();
        BeanUtils.copyProperties(sysUser, result);
        // 根据用户Id，获取到拥有的 权限列表
//        Set<SysPermission> permissions = sysPermissionService.findPermissionByUserId(sysUser.getUid());
//        List<ButtonVo> buttonVos = new ArrayList<>();
//        List<MenuVo> menuVos = new ArrayList<>();
//        if (permissions != null && permissions.size() > 0) {
//            permissions.forEach(permission -> {
//                if (permission.getType().toLowerCase().equals(PermissionType.BUTTON)) {
//                    /*
//                     * 如果权限是按钮，就添加到按钮里面
//                     * */
//                    buttonVos.add(
//                            new ButtonVo(
//                                    Math.toIntExact(permission.getPid()),
//                                    permission.getResources(),
//                                    permission.getTitle())
//                    );
//                }
//                if (permission.getType().toLowerCase().equals(PermissionType.MENU)) {
//                    /*
//                     * 如果权限是菜单，就添加到菜单里面
//                     * */
//                    menuVos.add(
//                            new MenuVo(
//                                    Math.toIntExact(permission.getPid()),
//                                    Math.toIntExact(permission.getParentId()),
//                                    permission.getTitle(),
//                                    "1".equals(permission.getVisible()),
//
//                                    permission.getPath(),
//
//                                    permission.getResources(),
//
//                                    null
//                            )
//                    );
//                }
//            });
//        }
//        result.setButtons(buttonVos);
//        result.setMenus(findRoots(menuVos));
        //新版权限与按钮
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userDetails.getUid());
        List<RouterVo> routerVos = menuService.buildMenus(menus);
        result.setMenus(routerVos);
        //按钮
        List<ButtonVo> buttonVos = menuService.selectButtonByUserId(userDetails.getUid());
        result.setButtons(buttonVos);

        Set<SysRole> roles = sysRoleService.findRolesByUserId(result.getUid());
        Set<String> rolesName = roles
                .stream()
                .map(SysRole::getDescription)
                .collect(Collectors.toSet());
        result.setRoles(rolesName);
        SysDepartment department = departmentMapper.selectById(sysUser.getDeptId());
        result.setDepartmentName(department.getName());
        return result;
    }

    @Override
    public IPage<SysUserVo> pagingQueryUsers(QueryParameter parameter) {
        if(parameter.getPageNum()<1||parameter.getPageSize()<1){
            return new Page<>();
        }
        List<SysUser> users=baseMapper.pageByKeywords(parameter);
        List<SysUserVo> collect = users.stream()
                .map(res -> {
                    SysUserVo sysUserVo = new SysUserVo();
                    BeanUtils.copyProperties(res, sysUserVo);
                    List<SysUserVo.RoleVo> roles = sysRoleService.findRolesByUserId(res.getUid())
                            .stream()
                            .map(role -> {
                                return new SysUserVo.RoleVo(role.getRid(), role.getRoleName(), role.getDescription());
                            })
                            .collect(Collectors.toList());
                    sysUserVo.setRoles(roles);
//                    String departmentName = departmentService.findById(res.getDeptId()).getName();
                    String departmentName = departmentService.getById(res.getDeptId()).getName();
                    sysUserVo.setDepartmentName(departmentName);
                    return sysUserVo;
                }).collect(Collectors.toList());
        Page<SysUserVo> result = new Page<>();
        result.setSize(parameter.getPageSize());
        result.setRecords(collect);
        result.setTotal(baseMapper.countByKeywords(parameter.getKeywords()));
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean userUpdateSelf(UserCanModifyInfo entity) {
        JwtUser jwtUser = (JwtUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(entity.getPassword()!=null){
            entity.setPassword(passwordEncoder.encode(entity.getPassword()));
        }
        tokenStorage().delete(jwtUser.getUsername());
        baseMapper.userUpdate(jwtUser.getUsername(),entity);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(Long uid) {
        SysUser user = this.getById(uid);
        if(user!=null){
            if(user.getUsername().equals(InitData.ROOT_USER)){
                throw new ParameterException("无法删除超级管理员");
            }
            String username = user.getUsername();
            tokenStorage().delete(username);
            baseMapper.deleteHaveRoles(uid);
            baseMapper.deleteById(uid);
        }else{
            throw new ParameterException("无此用户");
        }
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeById(Serializable uid) {
        // 删除用户拥有的角色
        baseMapper.deleteHaveRoles((Long) uid);
        return SqlHelper.retBool(baseMapper.deleteById((Long) uid));
    }

    @Override
    public SysUser getById(Serializable uid) {
        SysUser sysUser = baseMapper.selectById(uid);
        if(sysUser!=null){
            sysUser.setPassword(null);
        }
        return sysUser;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(UserCanCreateInfo entity) {
        if(baseMapper.queryUserByUsername(entity.getUsername())!=null){
            throw new UserExistsException("用户存在");
        }
        if (!entity.getRePassword().equals(entity.getPassword())){
            throw new ParameterException("密码与确认密码不相等");
        }
        String encodePassword = passwordEncoder.encode(entity.getPassword());
        entity.setPassword(encodePassword);
        SysUser sysUser=new SysUser();
        sysUser.setUsername(entity.getUsername());
        sysUser.setPassword(encodePassword);
        sysUser.setCreateTime(LocalDateTime.now());
        sysUser.setLastUpdateTime(LocalDateTime.now());
        sysUser.setStatus(UserStatus.USEFUL);
        sysUser.setDeptId(1L);
        sysUser.setGender(entity.getGender());
        sysUser.setEmail(entity.getEmail());
        sysUser.setNickname(entity.getNickname());
        if(entity.getGender()==null){
            sysUser.setGender(GenderConstant.UNKNOWN);
        }
        if(entity.getEmail()==null) {
            sysUser.setEmail("");
        }
        if(entity.getNickname()==null){
            sysUser.setNickname("");
        }
        baseMapper.insert(sysUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateDept(Long uid, Long deptId) {
        SysUser sysUser = baseMapper.selectById(uid);
        SysDepartment sysDepartment = departmentMapper.selectById(deptId);
        if(sysUser==null||sysDepartment==null){
            throw new ParameterException("未查到此用户或此部门");
        }
        if(InitData.ROOT_USER.equals(sysUser.getUsername())){
            throw new ParameterException("不能修改超级管理员用户信息");
        }
        tokenStorage().delete(sysUser.getUsername());
        baseMapper.updateUserDept(uid,deptId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(ManagerCanModifyInfo user) {
        if(user.getUid()==null){
            throw new ParameterException("无标识为"+user.getUid()+"的用户");
        }
        SysUser sysUser = baseMapper.selectById(user.getUid());
        if(InitData.ROOT_USER.equals(sysUser.getUsername())){
            throw new ParameterException("无法修改超级管理员");
        }
        if(user.getPassword()!=null){
            if (!user.getRePassword().equals(user.getPassword())){
                throw new ParameterException("密码与确认密码不相等");
            }
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
        tokenStorage().delete(sysUser.getUsername());
        BeanUtils.copyProperties(user,sysUser);
        baseMapper.updateById(sysUser);
        return true;
    }

    /**
     * @Description: 递归查找根节点
     * @param:       [allNodes]
     * @return:      java.util.List<MenuVo>
     * @Author:      liuqi
     * @CreateDate:  2020/4/1 0:17
     */
    private List<MenuVo> findRoots(List<MenuVo> allNodes) {
        // 根节点
        List<MenuVo> root = new ArrayList<>();
        allNodes.forEach(node -> {
            if (node.getParentId() == 0) {
                root.add(node);
            }
        });
        root.forEach(node -> {
            findChildren(node, allNodes);
        });
        return root;
    }

    /**
     * @Description: 递归查找子节点
     * @param:       [treeNode, treeNodes]
     * @return:      MenuVo
     * @Author:      liuqi
     * @CreateDate:  2020/4/1 0:15
     */
    private MenuVo findChildren(MenuVo treeNode, List<MenuVo> treeNodes) {
        for (MenuVo it : treeNodes) {
            if (treeNode.getPid().equals(it.getParentId())) {
                if (treeNode.getChildren() == null) {
                    treeNode.setChildren(new ArrayList<>());
                }
                treeNode.getChildren().add(findChildren(it, treeNodes));
            }
        }
        return treeNode;
    }

    /**
     * @Description: 修改用户权限
     * @param:       [uid, roleIds]
     * @return:      int
     * @Author:      liuqi
     * @CreateDate:  2020/4/1 0:15
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetUserRoles(Long uid, Set<Long> roleIds) {
        SysUser sysUser = baseMapper.selectById(uid);
        if(sysUser==null){
            throw new ParameterException("无标识为"+uid+"的用户");
        }
        if(InitData.ROOT_USER.equals(sysUser.getUsername())){
            throw new ParameterException("无法更改超级管理员用户信息");
        }
        for (Long roleId : roleIds) {
            if(roleMapper.selectById(roleId)==null){
                throw new ParameterException("没有标识为"+roleId+"的角色");
            }
        }
        List<Relation> collect = roleIds.stream()
                // 去除重复的角色ID
                .distinct()
                // 构造 Relation 对象
                .map(res -> new Relation(uid, res))
                .collect(Collectors.toList());
        tokenStorage().delete(baseMapper.queryUsernameById(uid));
        // 先删除当前用户拥有的所有角色
        baseMapper.deleteHaveRoles(uid);
        // 在赋值新的角色
        baseMapper.grantRoles(collect);
    }

}
